/*
 * @(#) HttpClientUtils.java 2016年2月3日
 *
 * Copyright 2010 NetEase.com, Inc. All rights reserved.
 */
package com.sfang.common.util.http;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.google.common.base.Stopwatch;
import com.sfang.common.base.entity.RestInfo;
import com.sfang.common.util.json.GsonUtil;
import io.netty.handler.codec.http.HttpMethod;
import org.apache.http.Consts;
import org.apache.http.Header;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.HttpClient;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.client.params.ClientPNames;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLContextBuilder;
import org.apache.http.conn.ssl.TrustStrategy;
import org.apache.http.conn.ssl.X509HostnameVerifier;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.params.HttpParams;
import org.apache.http.protocol.HTTP;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocket;
import java.io.IOException;
import java.nio.charset.Charset;
import java.security.GeneralSecurityException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;

/**
 * HttpClient工具类
 *
 * @author captcha_dev
 * @version 2016年2月3日
 */
public class HttpClientUtils {
    private static HttpClient HTTP_CLIENT = createHttpClient(200, 40, 10000, 10000, 10000);
    protected static Logger LOGGER = LoggerFactory.getLogger(HttpClientUtils.class);

    public static <T> RestInfo sendGet(String url, Map<String, String> map, TypeReference<T> clazz,
                                       Function<String, String> funcParameters,
                                       Function<String, String> func) {
        return sendGetReq(url, map, clazz, null, funcParameters, func);
    }

    public static <T> RestInfo sendGetForRlt(String url, Map<String, String> map, TypeReference<T> clazz,
                                             Function<String, String> funcParameters,
                                             Function<String, String> func) {
        return sendGetReq(url, map, clazz, null, funcParameters, func);
    }

    public static <T> RestInfo sendGetForCustomKeyValuePair(String url, Map<String, String> map, TypeReference<T> clazz,
                                                            Function<String, String> funcParameters,
                                                            Function<String, String> func) {
        return sendGetReq(url, map, clazz, null, funcParameters, func);
    }

    private static <T> RestInfo<T> assembly(String url, HttpMethod method, String params,
                                            String response, TypeReference<T> clazz) {

        try {
            T data = JSON.parseObject(response, new TypeReference<T>() {
            });
            return RestfulUtil.successRes(data);
        } catch (Exception e) {
            LOGGER.error("assembly error", e);
            return RestfulUtil.failRes(e.getMessage());
        }
    }

    public static <T> RestInfo sendGetReq(String url, Map<String, String> map, TypeReference<T> clazz, Map<String, String> headers,
                                          Function<String, String> funcParameters,
                                          Function<String, String> func) {

        LOGGER.info(String.format("sendGet url:%s params:%s", url, GsonUtil.toJsonString(map)));
        CloseableHttpResponse response = null;
        byte[] bytes = null;
        StringBuilder param = new StringBuilder("");

        try {
            //设置参数
            Iterator iterator = map.entrySet().iterator();
            boolean first = true;
            while (iterator.hasNext()) {
                Map.Entry<String, String> entry = (Map.Entry<String, String>) iterator.next();
                String key = entry.getKey();
                String value = entry.getValue();
                if (first) {
                    param.append("?").append(key).append("=").append(funcParameters != null ? funcParameters.apply(value) : value);
                    first = false;
                } else {
                    param.append("&").append(key).append("=").append(funcParameters != null ? funcParameters.apply(value) : value);
                }
            }

            url += param.toString();

            HttpGet httpUriRequest = new HttpGet(url);

            if (headers != null && headers.size() > 0)
                setHeaders(httpUriRequest, headers);

            String protocol = httpUriRequest.getURI().toString();

            if (protocol.startsWith("https")) {
                HTTP_CLIENT = createSSLInsecureClient();
            }

            //阻止自动重定向
            HttpParams params = httpUriRequest.getParams();
            params.setParameter(ClientPNames.HANDLE_REDIRECTS, false);
            Stopwatch stopwatch = Stopwatch.createStarted();
            response = (CloseableHttpResponse) HTTP_CLIENT.execute(httpUriRequest);
            stopwatch.stop();
            int statusCode = response.getStatusLine().getStatusCode();
            Map<String, String> responseHeaders = getHeaders(response);
            bytes = HttpServletUtil.getResponseContent(response);
            String rspValue = new String(bytes);
            LOGGER.info(String.format("sendGet url:%s elapsed:%s params:%s  response:%s", url, stopwatch.elapsed(TimeUnit.MILLISECONDS), GsonUtil.toJsonString(map), rspValue));

            if (func != null) {
                rspValue = func.apply(rspValue);
            }

            RestInfo rlt = new RestInfo();
            rlt.setHeaders(responseHeaders);
            rlt.setStatus(statusCode);

            if (statusCode == 200) {
                rlt = clazz == null ? RestfulUtil.successRes(rspValue)
                        : assembly(url, HttpMethod.GET, GsonUtil.toJsonString(map), rspValue, clazz);
            } else {
                rlt.setMsg(rspValue);
            }

            return rlt;

        } catch (Exception e) {
            LOGGER.error(String.format("sendGet error url:%s params:%s", url, GsonUtil.toJsonString(map)), e);
            return RestfulUtil.failRes(GsonUtil.toJsonString(e.getMessage()));
        } finally {
            if (response != null) {
                try {
                    response.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public static <T> RestInfo sendPostJSONForRlt(String url, Object params, Map<String, String> headers, TypeReference<T> clazz) {
        return postJSON(url, params, headers, clazz, null, null);
    }


    public static <T> RestInfo sendPostJSON(String url, Object params, Map<String, String> headers, TypeReference<T> clazz,
                                            Function<String, String> funcParameters,
                                            Function<String, String> func) {
        return postJSON(url, params, headers, clazz, funcParameters, func);
    }

    public static RestInfo sendPostJSON(String url, String params, Map<String, String> headers) {
        return postJSON(url, params, headers, null, null, null);
    }


    private static <T> RestInfo postJSON(String url, Object params, Map<String, String> headers, TypeReference<T> clazz,
                                         Function<String, String> funcParameters,
                                         Function<String, String> func) {

        try {
            LOGGER.warn(String.format("sendPostJSON url:%s params:%s", url, GsonUtil.toJsonString(params)));
            org.apache.http.client.methods.HttpPost httpPost = new org.apache.http.client.methods.HttpPost(url);
            //设置包头
            if (headers != null && headers.size() > 0)
                setHeaders(httpPost, headers);

            String paramsStringValue = params == null ? "" : (params instanceof String ? (String) params : JSON.toJSONString(params));

            if (funcParameters != null) {
                paramsStringValue = funcParameters.apply(paramsStringValue);
            }

            StringEntity entity = new StringEntity(paramsStringValue, HTTP.UTF_8);

            if (headers != null && headers.containsKey("contentType"))
                entity.setContentType(headers.get("contentType"));
            else
                entity.setContentType("application/json");

            httpPost.setEntity(entity);
            Stopwatch stopwatch = Stopwatch.createStarted();
            HttpResponse response = HTTP_CLIENT.execute(httpPost);
            stopwatch.stop();

            if (response.getHeaders("Content-Type")[0].getValue().startsWith("audio"))
                return RestfulUtil.successRes(response, "audio");

            if (clazz != null
                    && clazz.getType().getTypeName().contains("byte[]")) {
                LOGGER.info(String.format("sendPostJSON url:%s elapsed:%s params:%s", url, stopwatch.elapsed(TimeUnit.MILLISECONDS), GsonUtil.toJsonString(params)));
                return RestfulUtil.successRes(EntityUtils.toByteArray(response.getEntity()));
            }

            String result = EntityUtils.toString(response.getEntity(), Consts.UTF_8);
            if (func != null) {
                result = func.apply(result);
            }

            if (stopwatch.elapsed(TimeUnit.MILLISECONDS) > 100)
                LOGGER.warn(String.format("sendPostJSON slow url:%s elapsed:%s params:%s  response:%s", url, stopwatch.elapsed(TimeUnit.MILLISECONDS), GsonUtil.toJsonString(params), result));
            else
                LOGGER.info(String.format("sendPostJSON url:%s elapsed:%s params:%s  response:%s", url, stopwatch.elapsed(TimeUnit.MILLISECONDS), GsonUtil.toJsonString(params), result));


            int statusCode = response.getStatusLine().getStatusCode();
            Map<String, String> responseHeaders = getHeaders(response);

            RestInfo rlt = new RestInfo();
            rlt.setHeaders(responseHeaders);
            rlt.setStatus(statusCode);

            if (statusCode == 200) {
                rlt = clazz == null ? RestfulUtil.successRes(result)
                        : assembly(url, HttpMethod.POST, GsonUtil.toJsonString(params), result, clazz);
            } else {
                rlt.setMsg(result);
            }

            return rlt;
        } catch (Exception e) {
            LOGGER.error(String.format("sendPostJSON error url:%s params:%s", url, GsonUtil.toJsonString(params)), e);
            return RestfulUtil.failRes(e.getMessage());
        }
    }

    /**
     * 创建 SSL连接
     *
     * @return
     * @throws GeneralSecurityException
     */
    private static CloseableHttpClient createSSLInsecureClient() {
        try {
            SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null, new TrustStrategy() {
                public boolean isTrusted(X509Certificate[] chain, String authType) throws CertificateException {
                    return true;
                }
            }).build();

            SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext, new X509HostnameVerifier() {

                @Override
                public boolean verify(String arg0, SSLSession arg1) {
                    return true;
                }

                @Override
                public void verify(String host, SSLSocket ssl)
                        throws IOException {
                }

                @Override
                public void verify(String host, X509Certificate cert)
                        throws SSLException {
                }

                @Override
                public void verify(String host, String[] cns,
                                   String[] subjectAlts) throws SSLException {
                }

            });

            return HttpClients.custom().setSSLSocketFactory(sslsf).build();

        } catch (GeneralSecurityException e) {
            e.printStackTrace();
        }

        return null;
    }

    /**
     * 实例化HttpClient
     *
     * @param maxTotal
     * @param maxPerRoute
     * @param socketTimeout
     * @param connectTimeout
     * @param connectionRequestTimeout
     * @return
     */
    private static HttpClient createHttpClient(int maxTotal, int maxPerRoute, int socketTimeout, int connectTimeout,
                                               int connectionRequestTimeout) {
        RequestConfig defaultRequestConfig = RequestConfig.custom().setSocketTimeout(socketTimeout)
                .setConnectTimeout(connectTimeout).setConnectionRequestTimeout(connectionRequestTimeout).build();
        PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();
        cm.setMaxTotal(maxTotal);
        cm.setDefaultMaxPerRoute(maxPerRoute);
        CloseableHttpClient httpClient = HttpClients.custom().setConnectionManager(cm)
                .setDefaultRequestConfig(defaultRequestConfig).build();
        return httpClient;
    }

    /**
     * 发送post请求
     *
     * @param url      请求地址
     * @param params   请求参数
     * @param encoding 编码
     * @return
     */
    private static String sendPost(String url, Map<String, String> params, Charset encoding) {
        String resp = "";
        org.apache.http.client.methods.HttpPost httpPost = new org.apache.http.client.methods.HttpPost(url);
        if (params != null && params.size() > 0) {
            List<NameValuePair> formParams = new ArrayList<NameValuePair>();
            Iterator<Map.Entry<String, String>> itr = params.entrySet().iterator();
            while (itr.hasNext()) {
                Map.Entry<String, String> entry = itr.next();
                formParams.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));
            }
            UrlEncodedFormEntity postEntity = new UrlEncodedFormEntity(formParams, encoding);
            httpPost.setEntity(postEntity);
        }
        CloseableHttpResponse response = null;
        try {
            response = (CloseableHttpResponse) HTTP_CLIENT.execute(httpPost);
            Header header = response.getFirstHeader("Location");
            String location = header == null ? null : header.getValue();
            //加一个递归，把重定向的内容都获取完
            if (location != null) {
                resp = sendPost(location, null, encoding);
            } else {
                resp = EntityUtils.toString(response.getEntity(), encoding);
            }
        } catch (Exception e) {
            long now = System.currentTimeMillis();
            resp = "{\"success\":false,\"msg\":\"请求超时\",\"status\":408,\"businessCode\":0,\"serverTime\":" + now + ",\"error\":[{\"field\":\"错误信息\",\"defaultMessage\": " + GsonUtil.toJsonString(e) + "}]}";
            // log
            e.printStackTrace();
        } finally {
            if (response != null) {
                try {
                    response.close();
                } catch (IOException e) {
                    // log
                    e.printStackTrace();
                }
            }
        }
        return resp;
    }

    private static void setHeaders(HttpRequestBase httpRequestBase, Map<String, String> headers) {
        Iterator<Map.Entry<String, String>> iterator = headers.entrySet().iterator();
        while (iterator.hasNext()) {
            Map.Entry<String, String> entry = iterator.next();
            String name = entry.getKey();
            String value = entry.getValue();

            httpRequestBase.setHeader(name, value);
        }
    }

    private static Map<String, String> getHeaders(HttpResponse httpResponse) {
        Map<String, String> headerMap = new HashMap<String, String>();
        Header[] headers = httpResponse.getAllHeaders();
        for (Header header : headers) {
            String name = header.getName();
            String value = header.getValue();
            headerMap.put(name, value);
        }
        return headerMap;
    }
}
